<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>正则表达式括号的作用</title>
</head>

<body>
    <!-- 01分组和分支结构 -->
    <script>
        console.log("分组和分支结构:");
        //分组
        var regex = /(ab)+/g;
        var string = "ababa abbb ababab";
        console.log(string.match(regex)); //[abab ab ababab]
        //分支
        var regex = /^I love (JavaScript|Regular Expression)$/;
        console.log(regex.test("I love JavaScript")); //true
        console.log(regex.test("I love Regular Expression")); //true
    </script>

    <!-- 02分组引用 -->
    <script>
        "use strict";
        /* 
            1.括号提供了分组，便于我们引用它。引用某个分组，会有两种情形：在 JavaScript 里引用它，在正则表达式里引用它
            2.在匹配过程中，给每一个分组都开辟一个空间，用来存储每一个分组匹配到的数据

            ->分支里的括号也是可以被引用的,但是仅限于对匹配成功的那个分支
            举例：
                目标字符串：0551 （0551）
                正则：(0\d{3,4}-?|\(0\d{3,4}\)) ((0\d{3,4})-?|\(\2\))
                    第二个正则设想是用 \2 反向引用(0\d{3,4})，但匹配 0551 时，显然正则分支是不走那条路的；
                    匹配（0551）时，\2设想引用的是(0\d{3,4})，但是正则分支不走那条路；
                    最后分支内的反向引用总是危险的，但是RegExp.$总能访问到分支大括号匹配的内容
                    
            ->提取数据：match方法、exec方法、使用对象RegExp的全局属性$1至$9获取
            ->替换：replace方法

        */
        console.log("分组引用：");
        //1.提取数据
        //匹配日期
        var str = "2020-10-25";
        var reg = /\d{4}-\d{2}-\d{2}/;
        console.log(str.match(reg)); //["2020-10-25", index: 0, input: "2020-10-25", groups: undefined]
        var str = "2020-10-25";
        var reg = /(\d{4})-(\d{2})-(\d{2})/;
        console.log(str.match(
            reg)); //["2020-10-25", "2020", "10", "25", index: 0, input: "2020-10-25", groups: undefined]
        var reg = /(\d{4})-(\d{2})-(\d{2})/g;
        var str = "2017-06-12";
        console.log(reg.exec(
            str)); //["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]
        console.log(RegExp.$1); //2017
        console.log(RegExp.$2); //06
        console.log(RegExp.$3); //12
        //2.替换
        var reg = /(\d{4})-(\d{2})-(\d{2})/g;
        var str = "2017-06-12";
        //方式一：
        console.log(str.replace(reg, "$3/$2/$1")); //12/06/2017
        //方式二：
        console.log(str.replace(reg, () => {
            return `${RegExp.$3}/${RegExp.$2}/${RegExp.$1}`; //12/06/2017
        }));
        //方式三：
        console.log(str.replace(reg, (match, p1, p2, p3, offset, str) => {
            return p3 + "/" + p2 + "/" + p1; //12/06/2017
        }));
    </script>

    <!-- 03反向引用 -->
    <script>
        console.log("反向引用：");
        //匹配日期
        /* 
            格式：2016-06-12
                2016/06/12
                2016.06.12
         */
        var reg = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
        console.log(reg.test("2016-06-12")); //true
        console.log(reg.test("2016/06/12")); //true
        console.log(reg.test("2016.06.12")); //true
        console.log(reg.test("2016.06-12")); //true  显然这里是不符合要求的，数字间的符号必须保证相同
        //修正：使用\1\2\3...反向引用
        /* 
            ->注意里面的\1，表示的引用之前的那个分组 (-|\/|\.)，不管它匹配到什么（比如 -），\1都匹配那个同样的具体某个字符。
            ->存在的问题：
                -括号嵌套
                    以左括号（开括号）为准。

                -\10表示什么
                    >对象RegExp的全局属性$1至$9获取
                    >\10 是表示第 10 个分组
                    >如果真要匹配 \1 和 0 的话，请使用 (?:\1)0 或者 \1(?:0)。
                    >问：(\1)0与(?:\1)0的区别？（举例见下）
                      (\1)0 括号具有分组，引用的作用 捕获括号
                      (?:\1)0 括号仅是语法的要求 非捕获括号

                -引用不存在的分组会怎样？
                    >在正则里引用了不存在的分组时，此时正则不会报错，只是匹配反向引用的字符本身。
                    >注意 "\2" 表示对 "2" 进行了转义

                -分组后面有量词会怎样？
                    >分组后面有量词的话，分组最终捕获到的数据是最后一次的匹配
                    >举例：
                     (\d)+ 12345 --> 5
                     (\d+) 12345 --> 12345
        */
        var reg = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
        console.log(reg.test("2016.06-12")); //false
        console.log(reg.test("2016-06-12")); //true
        //存在的问题
        //括号嵌套
        /* 
            以左括号（开括号）为准。
            分析：
            1.括号的作用：分组，引用
            2.由1可知((\d)(\d(\d)))等价于\d\d\d,所以匹配的是123,\1\2\3\4匹配剩下的
            3.\1 123,\2 1,\3 23,\4 3
         */
        var reg = /^((\d)(\d(\d)))\1\2\3\4$/;
        var str = "1231231233";
        console.log(reg.test(str)); // true
        console.log(RegExp.$1); // 123
        console.log(RegExp.$2); // 1
        console.log(RegExp.$3); // 23
        console.log(RegExp.$4); // 3

        //\10表示什么
        /* 
            ->对象RegExp的全局属性$1至$9获取
            -> \10 是表示第 10 个分组
            -> 如果真要匹配 \1 和 0 的话，请使用 (?:\1)0 或者 \1(?:0)。
            问：(\1)0与(?:\1)0的区别？（举例见下）
             (\1)0 括号具有分组，引用的作用
             (?:\1)0 括号仅是语法的要求
         */
        var regex = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) (#{3})\11/;
        var string = "123456789# ######";
        console.log(regex.exec(
            string
        )); //["123456789# ######", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#", "###", index: 0, input: "123456789# ######", groups: undefined]
        console.log(RegExp.$9); //9
        console.log(RegExp.$11); //undefined
        //测试(?:p)
        console.log("helo".replace(/(?:l)/, "#")); //he#o
        var reg = /(123)(234)(?:\1)0/;
        console.log(reg.exec(
            "1232341230")); //["1232341230", "123", "234", index: 0, input: "1232341230", groups: undefined]
        var reg = /(123)(234)\1(?:0)/;
        console.log(reg.exec(
            "1232341230")); //["1232341230", "123", "234", index: 0, input: "1232341230", groups: undefined]
        var reg = /(123)(234)(\1)0/;
        console.log(reg.exec(
            "1232341230")); //["1232341230", "123", "234", "123", index: 0, input: "1232341230", groups: undefined]

        //引用不存在的分组会怎样？
        /* 
            ->在正则里引用了不存在的分组时，此时正则不会报错，只是匹配反向引用的字符本身。
            ->注意 "\2" 表示对 "2" 进行了转义
         */
        var regex = /\1\2\3\4\5\6\7\8\9/;
        console.log(regex.test("\1\2\3\4\5\6\7\8\9")); //true
        console.log("\1\2\3\4\5\6\7\8\9".split("")); //["", "", "", "", "", "", "", "8", "9"]
        console.log("123456789".split("")); //["1", "2", "3", "4", "5", "6", "7", "8", "9"]

        //分组后面有量词会怎样？
        /* 
            分组后面有量词的话，分组最终捕获到的数据是最后一次的匹配
            (\d)+ 12345 --> 5
            (\d+) 12345 --> 12345
         */
        var regex = /(\d)+/;
        var string = "12345";
        console.log(string.match(regex)); //["12345", "5", index: 0, input: "12345"]
        var regex = /(\d+)/;
        var string = "12345";
        console.log(string.match(regex)); //["12345", "12345", index: 0, input: "12345"]
        var regex = /(\d)+ \1/;
        console.log(regex.test("12345 1")); //false
        console.log(regex.test("12345 5")); //true
    </script>

    <!-- 04非捕获括号 -->
    <script>
        /* 
            ->括号的作用：分组，引用
            ->非捕获括号：只想要括号最原始的功能，但不会引用它
                (?:p) 和 (?:p1|p2|p3)
            ->捕获括号与非捕获括号的区别：使用RegExp.$测试 （见下）
        */
        console.log("非捕获符号：");
        var regex = /(?:ab)+/g;
        var string = "ababa abbb ababab";
        console.log(string.match(regex)); //["abab", "ab", "ababab"]
        console.log(RegExp.$1); //空

        var regex = /(ab)+/g;
        var string = "ababa abbb ababab";
        console.log(string.match(regex)); //["abab", "ab", "ababab"]
        console.log(RegExp.$1); //ab

        var regex = /^I love (?:JavaScript|Regular Expression)$/;
        console.log(regex.test("I love JavaScript")); //true
        console.log(regex.test("I love Regular Expression")); //true
        console.log(RegExp.$1); //空

        var regex = /^I love (JavaScript|Regular Expression)$/;
        console.log(regex.test("I love JavaScript")); //true
        console.log(regex.test("I love Regular Expression")); //true
        console.log(RegExp.$1); //Regular Expression
    </script>

    <!-- 05相关案例 -->
    <script>
        /* 
            1.字符串 trim 方法模拟
            ->一、匹配到开头和结尾的空白符，然后替换成空字符
            ->二、匹配整个字符串，然后用引用来提取出相应的数据
                -这里使用了惰性匹配 *?，不然也会匹配最后一个空格之前的所有空格的。
                -疑问：如何看待冲突？(.可以匹配到空格，\s也可以匹配到空格，但是由于匹配长度的影响，结果是不相同的，那么结果究竟是什么呢，以及为什么会这样？)
                比如：(.*?)(\s*)$ 惰性匹配
                      (.*)(\s*)$ 贪婪匹配
        */
        console.log("案例解析:");
        console.log("案例一：");
        console.log("   foot ball   ".replace(/^\s*|\s*$/g, "")); //foot ball
        console.log("   foot ball   ".replace(/^\s*(.*?)\s*$/g, "$1")); //foot ball
        var str = "   foot ball   ";
        var reg = /^\s*(.*?)(\s*)$/;
        console.log(reg.exec(str));//["   foot ball   ", "foot ball", "   ", index: 0, input: "   foot ball   ", groups: undefined]
        var str = "   foot ball   ";
        var reg = /^\s*(.*)(\s*)$/;
        console.log(reg.exec(str));//["   foot ball   ", "foot ball   ", "", index: 0, input: "   foot ball   ", groups: undefined]

        /* 2.将每个单词的首字母转换为大写
            举例：my name is epeli  -->  My Name Is Epeli
        */
        console.log("案例二：");
        var str = "my name is epeli";
        var reg = /(?:^|\s)\w/g;
        console.log(str.match(reg)); //m,n,i,e
        console.log(str.replace(reg, (c) => {
            return c.toLocaleUpperCase(); //My Name Is Epeli
        }));

        /* 3.驼峰化
            举例：-moz-transform  -->  "MozTransform"
        */
        console.log("案例三：");
        console.log("驼峰测试一：");
        var str = "-moz-transform   "; //结尾三个空格
        console.log(str.replace(/[-_\s]+(.)/g, (match, c) => {
            console.log(match);
            console.log(c);
            return c ? c.toLocaleUpperCase() : ""; //MozTransform 
            /* 无？时
                -m
                m
                -t
                t
                空格*3
                空格*1 (疑问：这里为什么会有一个空格呢？)
             */
        }));
        console.log("驼峰测试二：");
        console.log(str.replace(/[-_\s]+(.)?/g, (match, c) => {
            console.log(match);
            console.log(c);
            return c ? c.toLocaleUpperCase() : ""; //MozTransform 
            /* 有？时
               -m
               m
               -t
               t
               空格*3
               undefined
            */
        }));

        /*  4.中划线化(驼峰的逆过程)
         */
        console.log("案例四：");
        var str = " Moz  Transform";
        var reg = /([A-Z])/g;
        var str1 = str.replace(reg, "-$1");
        console.log(str1); // -Moz  -Transform
        var str2 = str1.replace(/[-_\s]+/g, "-");
        console.log(str2); //-Moz-Transform
        console.log(str2.toLowerCase()); //-moz-transform

        /* 5.HTML 转义和反转义
            ->补充知识点：
                -for-in访问属性会以大小顺序访问
                -Object.keys(Obj) 按照大小顺序降属性排列到数组中，并返回
                -replace第二个参数：数的返回值作为替换字符串，返回值会替换每次匹配到的那一部分。
                -new RegExp()参数：字符串形式的reg，特殊标志 （特别注意字符串形式的reg要转义）
            ->思路：
                创建一个对象，该对象的属性是要匹配的字符，通过replace方法匹配到字符后，再通过返回“对象[字符]”的方式来替换字符，即对象的 ”属性-属性值“ 对应着 “目标项-替换项”
         */
        //补充：会按照（大小）顺序访问属性
        /* var obj = {
            1:12,
            10:13,
            2:14,
        }
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                console.log(key);
            }
        } */
        console.log("案例五：");
        // 将HTML特殊字符转换成等值的实体
        function func(str) {
            var example = {
                "<" : "左尖括号",
                ">" : "右尖括号",
                " " : "空格",
                "'" : "单引号",
            };
            var reg = new RegExp('['+Object.keys(example).join("")+']','g');// 正则：/[<> ']/
            return str.replace(reg,(match)=>{
                return example[match];
            });
        }
        var str = func("<div>Blah blah'blah</div>");
        console.log(str);// 左尖括号div右尖括号Blah空格blah单引号blah左尖括号/div右尖括号
        // 实体字符转换为等值的HTML。
        function func1(str){
            var example = {
                "左尖括号":"<",
                "右尖括号":">",
                "空格":" ",
                "单引号":"'",
            };
            var reg = new RegExp(`(${Object.keys(example).join('|')})`,'g');// 正则：/(左|右|空|单)/
            return str.replace(reg,(match,key)=>{
                if(example.hasOwnProperty(key)) return example[key];
                return match;
            });
        }
        var str = func1(str);
        console.log(str);// <div>Blah blah'blah</div>

        /* 6.匹配成对标签
            要求：
                匹配 <p>laoyao bye bye</p>
                不匹配 <title>wrong!</p>
        */
        console.log("案例六：");
        var reg = /(<([a-z]+)>).*(<\/(\2)>)/g;
        var str = "<p>laoyao bye bye</p>";
        // var str = "<title>wrong!</p>";
        console.log(reg.exec(str));// ["<p>laoyao bye bye</p>", "<p>", "p", "</p>", "p", index: 0, input: "<p>laoyao bye bye</p>", groups: undefined]
    </script>
</body>

</html>